<?php
class ModuleProfile
{

    /**
     * @var ModuleProfile
     */
    private static $instance;

    /**
     * Get instance of ModuleProfile.
     *
     * @return ModuleProfile $instance
     */
    public static function get()
    {
        if (!self::$instance) {
            self::$instance = new ModuleProfile();
        }
        return self::$instance;
    }

    /**
     * Initialize.
     */
    private function __construct()
    {
    }

    /**
     * Create user profile record.
     *
     * @param int $userId
     * @return ModuleProfile $profile
     */
    public function createProfile($userId)
    {
        $initToken = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, ModuleProfileConst::CONFIG_KEY_INIT_TOKEN, true);
        $initToken = $initToken['value'];
        $initEnergy = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, ModuleProfileConst::CONFIG_KEY_INIT_ENERGY, true);
        $initEnergy = $initEnergy['value'];
        $energyLimit = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, ModuleProfileConst::CONFIG_KEY_ENERGY_LIMIT, true);
        $energyLimit = $energyLimit['value'];
        $timestamp = SzTime::getTime();

        $profile = new ModuleProfileVo(
            $userId, // user id
            1, // level
            0, // exp
            $initToken,  // freeToken
            0,  // purchasedToken
            0,  // commodityValue
            $initEnergy, // energy
            $energyLimit, // energy limit
            $timestamp,  // last energy charged time
            $timestamp,  // register time
            null,  // last login
            0,
            0,
            true // insert action
        );
        SzPersister::get()->setVo($profile);

        return $profile;
    }

    /**
     * Update user profile money.
     *
     * @param ModuleProfileVo $profile
     * @param int $token positive number to add money; negative number to remove money
     * @param boolean $allowExceed default false
     * @param int $commodityValue default 0，it shall always be positive number
     * @throws SzException 30001
     * @return ModuleProfileVo $profile
     */
    public function updateProfileToken($profile, $token, $allowExceed = false, $commodityValue = 0)
    {
        if ($commodityValue > 0 && $token > 0) {
            // 增加token，并且价值大于0，默认为增加付费货币
            $isFreeToken = false;
        } else {
            // 增加token，并且价值小于等于0，默认为增加免费货币
            // 扣除token，默认为扣除免费货币，免费货币不足，扣除付费货币，并计算扣除的价值
            $isFreeToken = true;
            $commodityValue = 0;
        }

        // when token is allow exceed or update purchased token or reduce token, we didn't judge limit
        if ($allowExceed || $isFreeToken == false || $token < 0) {
            $currentToken = $profile->getToken() + $token;
        } else {
            $tokenLimit = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, ModuleProfileConst::CONFIG_KEY_TOKEN_LIMIT, true);
            $tokenLimit = $tokenLimit['value'];

            // if token was exceeded the limit, no need to update
            if ($profile->getToken() >= $tokenLimit) {
                return $profile;
            } else {
                $currentToken = min($profile->getToken() + $token, $tokenLimit);
            }
        }

        // validate token enough or not
        if ($token < 0 && $currentToken < 0) {
            throw new SzException(30001, null, ModuleProfileConst::MODULE_NAME); // not enough token
        }

        $changeToken = $currentToken - $profile->getToken();
        if ($isFreeToken || $token < 0) {
            if ($token < 0 && $profile->getFreeToken() < abs($token)) {
                // means bonus token not enough to pay the amount, also need to minus purchased token
                $changeToken = abs($profile->getFreeToken() + $token);
                $changeCommodityValue = -$changeToken * ($profile->getCommodityValue() / $profile->getPurchasedToken());
                $profile->setFreeToken(0);
                $profile->updatePurchasedToken(-$changeToken);
                $profile->updateCommodityValue($changeCommodityValue);
            } else {
                $profile->updateFreeToken($changeToken);
            }
        } else {
            if ($isFreeToken) {
                $profile->updateFreeToken($changeToken);
            } else {
                $profile->updatePurchasedToken($changeToken);
                $profile->updateCommodityValue($commodityValue);
            }
        }

        return $profile;
    }
    /**
     * Update user profile exp, also handle user level up case.
     *
     * @param ModuleProfileVo $profile
     * @param int $exp positive numbers
     * @param boolean $canUpgradeLastLevel default true
     * @param boolean $continuousMode default false
     * @return array '$profile => ModuleProfileVo, $isLevelUp => boolean, $items => array(itemId => count)'
     */
    public function updateProfileExp($profile, $exp, $canUpgradeLastLevel = true, $continuousMode = false)
    {
        $isLevelUp = false;
        $items = array(); // for bonus items when profile levelup

        if ($exp > 0) {
            $profile->setExp($profile->getExp() + $exp);
            list($profile, $isLevelUp) = $this->processUserExpUpdate($profile, $items, $canUpgradeLastLevel, $continuousMode);
        }

        return array($profile, $isLevelUp, $items);
    }

    /**
     * Process user exp update.
     *
     * @param ModuleProfileVo $profile
     * @param array $items passed as reference
     * @param boolean $canUpgradeLastLevel default true
     * <pre>
     *      When $canUpgradeLastLevel is false, update exp can not upgrade to the last level
     * </pre>
     * @param boolean $continuousMode default false
     * @return array(
     *      ModuleProfileVo $profile,
     *      boolean $isLevelUp
     * )
     */
    private function processUserExpUpdate($profile, &$items, $canUpgradeLastLevel = true, $continuousMode = false)
    {
        $isLevelUp = false;
        $levelUpBonusSettings = null;
        $expLadder = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);

        $originLevel = $profile->getLevel();
        $finalLevel = $originLevel;
        $exp = $profile->getExp();

        SzUtility::calcExpUpdate($finalLevel, $exp, $expLadder, $continuousMode);

        // if can not upgrade last level, user exp will not add up exceed this level max exp
        if (!$canUpgradeLastLevel) {
            $ladderKeys = array_keys($expLadder);
            $lastLevel = array_pop($ladderKeys);
            if ($lastLevel == $finalLevel) {
                $finalLevel = $originLevel;
                $finalExp = $expLadder[$lastLevel]['exp'];
                if ($profile->getExp() > $finalExp) {
                    $profile->setExp($finalExp);
                }
            }
        }

        // there is some level up events
        if ($finalLevel != $originLevel) {
            $isLevelUp = true;
            // set updated data
            $profile->setLevel($finalLevel);
            $profile->setExp($exp);
            // handle level up bonus items
            if (is_null($levelUpBonusSettings)) {
                $levelUpBonusSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELUP_BONUS, null, true);
            }
            $items = SzUtility::handleLevelUpBonus($originLevel, $finalLevel, $levelUpBonusSettings);
        }

        return array($profile, $isLevelUp);
    }

    /**
     * Update profile energy change, including add energy & reduce energy.
     *
     * @param ModuleProfileVo $profile
     * @param int $energy positive to add energy, negative to reduce energy
     * @param int $reqTime
     * @param boolean $allowExceed default false
     * @throws SzException 30002
     * @return ModuleProfileVo $profile
     */
    public function updateProfileEnergy($profile, $energy, $reqTime, $allowExceed = false)
    {
        $limit = $profile->getEnergyLimit();
        $originEnergy = $profile->getEnergy();

        if ($allowExceed) {
            $changedEnergy = $originEnergy + $energy;
        } else {
            $changedEnergy = min($originEnergy + $energy, $limit);
        }

        if ($changedEnergy < 0) {
            throw new SzException(30002, null, ModuleProfileConst::MODULE_NAME); // not enough energy
        }

        $profile->setEnergy($changedEnergy);
        if ($originEnergy >= $limit && $changedEnergy < $limit) {
            $profile->setLastEnergyChargedTime($reqTime);
        }

        return $profile;
    }

    /**
     * Refresh profile energy according to now time & last energy charged time.
     *
     * @param ModuleProfileVo $profile
     * @param int $reqTime
     * @throws SzException 10100
     * @return ModuleProfileVo $profile
     */
    public function refreshProfileEnergy($profile, $reqTime)
    {
        // read configuration
        $settings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, null, true);
        $recoveryInterval = $settings[ModuleProfileConst::CONFIG_KEY_CHARGE_INTERVAL]['value'];
        $recoveryAmount = $settings[ModuleProfileConst::CONFIG_KEY_CHARGE_AMOUNT]['value'];
        if (!$recoveryInterval) {
            throw new SzException(10100, array(ModuleProfileConst::CONFIG_KEY_CHARGE_INTERVAL, ModuleProfileConst::CONFIG_PROFILE), ModuleProfileConst::MODULE_NAME);
        } else if (!$recoveryAmount) {
            throw new SzException(10100, array(ModuleProfileConst::CONFIG_KEY_CHARGE_AMOUNT, ModuleProfileConst::CONFIG_PROFILE), ModuleProfileConst::MODULE_NAME);
        }

        list($energy, $lastChargedTime) = SzUtility::refreshRegularlyGeneratedResourceCount(
            $profile->getEnergy(),
            $profile->getLastEnergyChargedTime(),
            $profile->getEnergyLimit(),
            $recoveryInterval,
            $recoveryAmount,
            $reqTime
        );

        $profile->setEnergy($energy);
        $profile->setLastEnergyChargedTime($lastChargedTime);

        return $profile;
    }
}